package modeler;


import javax.media.opengl.GL;
import javax.media.opengl.GLAutoDrawable;
import javax.vecmath.Vector2f;
import javax.vecmath.Vector3f;

import modeler.ui.TransformationAttributePanel;

public abstract class Manip {

  //Constant picking names for the manipulator handle
  public static final int PICK_X = 10;
  public static final int PICK_Y = 11;
  public static final int PICK_Z = 12;
  public static final int PICK_OTHER = 13;
  public static final double BOX_RADIUS = 0.1;

  public static final byte X_AXIS = 0;
  public static final byte Y_AXIS = 1;
  public static final byte Z_AXIS = 2;
  
  Vector3f e0 = new Vector3f(0,0,0);
  Vector3f eX = new Vector3f(1,0,0);
  Vector3f eY = new Vector3f(0,1,0);
  Vector3f eZ = new Vector3f(0,0,1);
  
  //more
  protected Vector2f pickedMousePoint = new Vector2f(); // the mouseEvent that led to this manip being picked
  protected Transformation t; // transformations this manipulator modifies
  protected Camera c; // camera associated with the active viewport
  protected int axisMode; // which axis is currently being dragged

  /**
   * Determines the action of the manipulator when it it dragged.  Responsible 
   * for computing the change the manipulators transformation from the 
   * supplied mousePosition and the current mouseDelta.
   * @param mousePosition
   * @param mouseDelta
   */
  public abstract void dragged(Vector2f mousePosition, Vector2f mouseDelta);

  // Given two lines, one through points a and b, the other through the 
  // points c and d, compute a value t that minimizes the distance from
  // the point (tb + (1-t)a) to the line through c and d. 
  public float lineNearLine(Vector3f a, Vector3f b, Vector3f c, Vector3f d) {
          double t = 0;
          Vector3f ab = new Vector3f(b);
          ab.sub(a);
          
          Vector3f cd = new Vector3f(d);
          cd.sub(c);
          
          // compute edge vectors of a plane 
          // e1 = d-c  and
          // e2 = v1 cross (b-a)
          Vector3f e1 = new Vector3f(cd);
          Vector3f e2 = new Vector3f();
          e2.cross(e1, ab);

          // intersect the line ab with the plane
          // passing through c with edge vectors e1, e2, to yield t
          Vector3f p = new Vector3f();
          Vector3f s = new Vector3f();
          Vector3f q = new Vector3f();
          
          p.cross(ab,e2);
          double r = e1.dot(p);
          
          if (r < 1e-5 && r > -1e-5) return 0;
          double f = (1.0 / r);
          s.set(a);
          s.sub(c);
          q.cross(s,e1);
          t = f * e2.dot(q);
          // jack's your uncle
          return (float)t;
    
  }
  
  public void nonParallelVector(Vector3f v, Vector3f nonParallel) {
          if (v.x <= v.y && v.x <= v.z) nonParallel.set(1,0,0);
          else if (v.y <= v.x && v.y <= v.z) nonParallel.set(0,1,0);
          else if (v.z <= v.x && v.z <= v.y) nonParallel.set(0,0,1);
  }
  
  // Computes normalized transformed versions of the standard basis vectors eX, eY, eZ
  // up to and including this transform's parent, but not this transform.  It ignores
  // translation.
  public void normalizedTransformedAxes(Vector3f outX, Vector3f outY, Vector3f outZ) {
          transformedAxes(outX, outY, outZ);
          outX.normalize();
          outY.normalize();
          outZ.normalize();
  }
  
  
  private Vector3f loc = new Vector3f();

  // Computes transformed versions of the standard basis vectors eX, eY, eZ
  // up to and including this transform's parent, but not this transform.  It ignores
  // translation.
  public void transformedAxes(Vector3f outX, Vector3f outY, Vector3f outZ) {
        
          if (t.getParent() != null) {
                  ((Transformation)t.getParent()).toWorld(e0, loc);
                  ((Transformation)t.getParent()).toWorld(eX, outX);
                  ((Transformation)t.getParent()).toWorld(eY, outY);
                  ((Transformation)t.getParent()).toWorld(eZ, outZ);
                  outX.sub(loc);
                  outY.sub(loc);
                  outZ.sub(loc);
          } else {
                  outX.set(eX);
                  outY.set(eY);
                  outZ.set(eZ);
          }
          
  }
  
  public void normalizedTransformedAxes2(Vector3f outX, Vector3f outY, Vector3f outZ) {
          transformedAxes2(outX, outY, outZ);
          outX.normalize();
          outY.normalize();
          outZ.normalize();
  }
  
  
//Computes transformed versions of the standard basis vectors eX, eY, eZ
  // up to and including this transform's parent, but not this transform.  It ignores
  // translation.
  public void transformedAxes2(Vector3f outX, Vector3f outY, Vector3f outZ) {
          
          if(GLView.drawInPickVertMode && GLView.currentVertex != null) {
                  e0.set(GLView.currentVertex);
          
                  if (t.getParent() != null) {
                          
                          eX.set(1,0,0);
                          eY.set(0,1,0);
                          eZ.set(0,0,1);
                          
                          ((Transformation)t.getParent()).toWorld(GLView.currentVertex, loc);
                          eX.add(GLView.currentVertex);
                          eY.add(GLView.currentVertex);
                          eZ.add(GLView.currentVertex);
                          }
                        
                          ((Transformation)t.getParent()).toWorld(eX, outX);
                          ((Transformation)t.getParent()).toWorld(eY, outY);
                          ((Transformation)t.getParent()).toWorld(eZ, outZ);
                          outX.sub(loc);
                          outY.sub(loc);
                          outZ.sub(loc);
                  } else {
                          outX.set(eX);
                          outY.set(eY);
                          outZ.set(eZ);
                  }
  }
 

  // Like above, but takes x,y,z axes as input instead of assuming the standard basis.
  public void transformAxes(Vector3f x, Vector3f y, Vector3f z) {
          Vector3f loc = new Vector3f();
      e0.set(0, 0, 0);
          
          if (t.getParent() != null) {
                  ((Transformation)t.getParent()).toWorld(e0, loc);
                  ((Transformation)t.getParent()).toWorld(x, x);
                  ((Transformation)t.getParent()).toWorld(y, y);
                  ((Transformation)t.getParent()).toWorld(z, z);
                  x.sub(loc);
                  y.sub(loc);
                  z.sub(loc);
          }
  }
  
  // Returns a vector indicating the world-space position of the
  // origin of this manipulator, taking into account all the
  // transformations acting on it.
  public Vector3f getWorldPosition() {
    Vector3f wpos = new Vector3f();
    return wpos;
  }

  ////////////////////////////////////////////////////////////////////////////////////////////////////
  // Various methods for controlling manipulators
  ////////////////////////////////////////////////////////////////////////////////////////////////////
  
  public void released() {
    axisMode = 0;
    refreshTransformationAttributePanel();
  }

  public void setPickedInfo(int newAxis, Camera camera, Vector2f lastMousePoint) {
    this.pickedMousePoint.set(lastMousePoint); 
    c = camera;
    axisMode = newAxis;
  }
 
  public void setTransformation(Transformation t) {
    this.t = t;
  }

  public void refreshTransformationAttributePanel() {
          if( t.getUserObject() != null ) {
                  ((TransformationAttributePanel)t.getUserObject()).refresh();
          }
  }
  
  ////////////////////////////////////////////////////////////////////////////////////////////////////
  // Methods for rendering the manipulators
  ////////////////////////////////////////////////////////////////////////////////////////////////////
  
  public boolean drawEnabled = true;
  public abstract void glRender(GLAutoDrawable d, double scale);
  
  // gl-rotates the Y axis to the given vector using
  // an arbitrary frame
  private Vector3f ortho1 = new Vector3f();
  private Vector3f ortho2 = new Vector3f();
  public void glRotateTo(GL gl, Vector3f v) {
      nonParallelVector(v, ortho1);
      ortho2.cross(v, ortho1);
      ortho1.cross(ortho2, v);
      ortho1.normalize();
      ortho2.normalize();
      gl.glMultMatrixf(new float[] {
          ortho1.x, ortho1.y, ortho1.z, 0,
          v.x, v.y, v.z, 0,
          ortho2.x, ortho2.y, ortho2.z, 0,
          0,0,0, 1 }, 0);
  }
  
  public void glRenderRotation(GL gl, Transformation t) {
      gl.glRotated(t.R.z, 0, 0, 1);
      gl.glRotated(t.R.y, 0, 1, 0);
      gl.glRotated(t.R.x, 1, 0, 0);
  }
  
  // openGL-ifies all the rotations that come before the current transformation
  public void glDoOrientation(GL gl, Transformation t) {
    glDoOrientationHelper(gl, t, t);
  }
  
  public void glDoOrientationHelper(GL gl, Transformation t, Transformation originalT) {
    if (t.getParent() == null) {
        glRenderRotation(gl, t);
    } else {
        glDoOrientationHelper(gl, (Transformation) t.getParent(), originalT);
        if (t == originalT) return;
        glRenderRotation(gl, t);
    }
  }
  
  Vector3f location = new Vector3f(0,0,0);
  public void glDoLocation(GL gl, Transformation t) {
    t.toWorld(e0, location);
    gl.glTranslatef(location.x, location.y, location.z);
  }
  
  private double constantSize = 0.3;

  public void renderConstantSize(GLAutoDrawable d, Camera camera) {
    if (!drawEnabled) return;
    double scale = camera.getHeight() * constantSize;
    glRender(d, scale);
  }
  
  private static double arrowDivs = 32;
  private static double arrowTailRadius = 0.05;
  private static double arrowHeadRadius = 0.11;

  public static void glRenderArrow(GL gl) {
          glRenderArrow(Y_AXIS, gl);
  }

  public static void glRenderBox(GL gl) {

    double r = BOX_RADIUS;
    gl.glBegin(GL.GL_QUADS);
    {
      gl.glNormal3d(1, 0, 0);
      gl.glVertex3d(r, r, r);
      gl.glVertex3d(r, -r, r);
      gl.glVertex3d(r, -r, -r);
      gl.glVertex3d(r, r, -r);

      gl.glNormal3d(-1, 0, 0);
      gl.glVertex3d(-r, r, -r);
      gl.glVertex3d(-r, -r, -r);
      gl.glVertex3d(-r, -r, r);
      gl.glVertex3d(-r, r, r);

      gl.glNormal3d(0, 1, 0);
      gl.glVertex3d(r, r, r);
      gl.glVertex3d(r, r, -r);
      gl.glVertex3d(-r, r, -r);
      gl.glVertex3d(-r, r, r);

      gl.glNormal3d(0, -1, 0);
      gl.glVertex3d(-r, -r, r);
      gl.glVertex3d(-r, -r, -r);
      gl.glVertex3d(r, -r, -r);
      gl.glVertex3d(r, -r, r);

      gl.glNormal3d(0, 0, 1);
      gl.glVertex3d(r, r, r);
      gl.glVertex3d(-r, r, r);
      gl.glVertex3d(-r, -r, r);
      gl.glVertex3d(r, -r, r);

      gl.glNormal3d(0, 0, -1);
      gl.glVertex3d(r, -r, -r);
      gl.glVertex3d(-r, -r, -r);
      gl.glVertex3d(-r, r, -r);
      gl.glVertex3d(r, r, -r);

    }
    gl.glEnd();
  }
  
  public static void glRenderArrow(byte axis, GL gl) {

    gl.glPushMatrix();
    switch (axis) {
      case X_AXIS:
        gl.glRotatef(90f, 0, 0, -1);
        break;
      case Z_AXIS:
        gl.glRotatef(90f, 1, 0, 0);
    }
    // tail coney
    double theta = 0;
    gl.glBegin(GL.GL_TRIANGLE_FAN);
    gl.glVertex3d(0d, 0d, 0d);
    for (double i = 0; i <= arrowDivs; ++i) {
      theta = (i / arrowDivs) * Math.PI * 2;
      gl.glVertex3d(Math.cos(theta) * arrowTailRadius, 1.8, Math.sin(theta) * arrowTailRadius);
    }
    gl.glEnd();

    // neck ring
    gl.glBegin(GL.GL_QUAD_STRIP);
    for (double i = 0; i <= arrowDivs; ++i) {
      theta = (i / arrowDivs) * Math.PI * 2;
      gl.glVertex3d(Math.cos(theta) * arrowTailRadius, 1.8, Math.sin(theta) * arrowTailRadius);
      gl.glVertex3d(Math.cos(theta) * arrowHeadRadius, 1.83, Math.sin(theta) * arrowHeadRadius);
    }
    gl.glEnd();

    // head coney
    gl.glBegin(GL.GL_TRIANGLE_FAN);
    gl.glVertex3d(0, 2, 0);
    for (double i = 0; i <= arrowDivs; ++i) {
      theta = (i / arrowDivs) * Math.PI * 2;
      gl.glVertex3d(Math.cos(theta) * arrowHeadRadius, 1.83, Math.sin(theta) * arrowHeadRadius);
    }
    gl.glEnd();

    gl.glPopMatrix();
  }
  
  public static void glRenderCircle(byte axis, GL gl) {
    gl.glPushMatrix();
    switch (axis) {
      case X_AXIS:
        gl.glRotatef(90f, 0, 0, -1);
        break;
      case Z_AXIS:
        gl.glRotatef(90f, 1, 0, 0);
    }

    // neck ring
    gl.glBegin(GL.GL_LINE_LOOP);
    for (double i = 0; i <= arrowDivs; ++i) {
      double theta = (i / arrowDivs) * Math.PI * 2;
      gl.glVertex3d(Math.cos(theta) * 2, 0, Math.sin(theta) * 2);
    }
    gl.glEnd();

    gl.glPopMatrix();
  }
  
  public void glRenderBoxOnAStick(GL gl) {
          glRenderBoxOnAStick(Y_AXIS, gl);
  }
  
  public void glRenderBoxOnAStick(byte axis, GL gl) {
          gl.glPushMatrix();
          switch (axis) {
          case X_AXIS:
                  gl.glRotatef(90f, 0, 0, -1);
                  break;
          case Z_AXIS:
                  gl.glRotatef(90f, 1, 0, 0);
          }

          gl.glPushAttrib(GL.GL_CURRENT_BIT);
          gl.glColor4f(1,1,1,1);
          gl.glBegin(GL.GL_LINES);
          gl.glVertex3f(0,0,0);
          gl.glVertex3f(0,2,0);
          gl.glEnd();
          gl.glPopAttrib();
          
          gl.glTranslatef(0,2,0);
          glRenderBox(gl);
          
          gl.glPopMatrix();
  }

}
